package drds.datasource.connection_pool.impl;

//import drds.common.Constants;

import drds.datasource.connection_pool.DataSourceState;
import drds.datasource.connection_pool.api.SqlException;
import drds.datasource.connection_pool.api.sql_exception.DataSourceClosedException;
import drds.datasource.connection_pool.api.sql_exception.DataSourceDisableException;
import drds.datasource.connection_pool.api.sql_exception.DataSourceNotAvailableException;
import drds.datasource.connection_pool.api.sql_exception.GetConnectionTimeoutException;
import drds.datasource.connection_pool.impl.holder.ConnectionHolder;
import drds.datasource.connection_pool.impl.thread.CreateConnectionTask;
import drds.datasource.connection_pool.impl.thread.CreateConnectionThread;
import drds.datasource.connection_pool.impl.thread.DestroyConnectionTask;
import drds.datasource.connection_pool.impl.thread.DestroyConnectionThread;
import drds.datasource.connection_pool.util.JdbcUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import tool.plus.Constants;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class DataSource extends AbstractDataSource {
    @Setter
    @Getter
    public static final AtomicLongFieldUpdater<DataSource> recycleErrorCountUpdater = AtomicLongFieldUpdater
            .newUpdater(DataSource.class, "recycleErrorCount");
    @Setter
    @Getter
    public static final AtomicLongFieldUpdater<DataSource> connectErrorCountUpdater = AtomicLongFieldUpdater
            .newUpdater(DataSource.class, "connectErrorCount");
    @Setter
    @Getter
    public static final AtomicLongFieldUpdater<DataSource> resetCountUpdater = AtomicLongFieldUpdater
            .newUpdater(DataSource.class, "resetCount");
    @Setter
    @Getter
    public static final AtomicLongFieldUpdater<DataSource> createConnectionTaskRunnableIdSeedUpdater = AtomicLongFieldUpdater
            .newUpdater(DataSource.class, "createTaskIdSeed");
    public final static String DRUID_TIME_BETWEEN_LOG_STATS_MILLIS = "druid.timeBetweenLogStatsMillis";
    @Setter
    @Getter
    public static ThreadLocal<Long> waitNanosLocal = new ThreadLocal<Long>();
    @Setter
    @Getter
    public final CountDownLatch iniCountDownLatch = new CountDownLatch(2);
    @Setter
    @Getter
    public boolean killWhenSocketReadTimeout = false;
    /**
     * poolingCount=poolingIndex+1
     */
    @Setter
    @Getter
    public int poolingCount = 0;
    @Setter
    @Getter
    public int activeCount = 0;
    @Setter
    @Getter
    public volatile long discardCount = 0;
    @Setter
    @Getter
    public int notEmptyWaitThreadCount = 0;
    @Setter
    @Getter
    public DestroyConnectionTask destroyConnectionTask;
    @Setter
    @Getter
    public volatile Future<?> createSchedulerFuture;
    @Setter
    @Getter
    public volatile boolean closing = false;
    @Setter
    @Getter
    public volatile boolean closed = false;
    @Setter
    @Getter
    public volatile boolean keepAlive = false;
    @Setter
    @Getter
    public boolean asyncInit = false;
    // stats
    @Setter
    @Getter
    private volatile long recycleErrorCount = 0L;
    @Setter
    @Getter
    private long connectCount = 0L;
    @Setter
    @Getter
    private long closeCount = 0L;
    @Setter
    @Getter
    private volatile long connectErrorCount = 0L;
    @Setter
    @Getter
    private long recycleCount = 0L;
    @Setter
    @Getter
    private long removeAbandonedCount = 0L;
    @Setter
    @Getter
    private long notEmptyWaitCount = 0L;
    @Setter
    @Getter
    private long notEmptySignalCount = 0L;
    @Setter
    @Getter
    private long notEmptyWaitNanos = 0L;
    @Setter
    @Getter
    private int keepAliveCheckCount = 0;
    @Setter
    @Getter
    private int activePeak = 0;
    @Setter
    @Getter
    private long activePeakTime = 0;
    @Setter
    @Getter
    private int poolingPeak = 0;
    @Setter
    @Getter
    private long poolingPeakTime = 0;
    // store
    @Setter
    @Getter
    private volatile ConnectionHolder[] connectionHolders;
    @Setter
    @Getter
    private int notEmptyWaitThreadPeak = 0;
    //
    @Setter
    @Getter
    private ConnectionHolder[] evictConnectionHolders;
    @Setter
    @Getter
    private ConnectionHolder[] keepAliveConnectionHolders;
    // threads
    @Setter
    @Getter
    private volatile ScheduledFuture<?> destroySchedulerFuture;
    @Setter
    @Getter
    private CreateConnectionThread createConnectionThread;
    @Setter
    @Getter
    private DestroyConnectionThread destroyConnectionThread;
    @Setter
    @Getter
    private int createConnectionTaskCount;
    @Setter
    @Getter
    private volatile long createTaskIdSeed = 1L;
    @Setter
    @Getter
    private long[] createConnectionTasks;
    @Setter
    @Getter
    private volatile boolean enable = true;
    @Setter
    @Getter
    private boolean resetStatEnable = true;
    @Setter
    @Getter
    private volatile long resetCount = 0L;
    @Setter
    @Getter
    private long closeTimeMillis = -1L;
    @Setter
    @Getter
    private boolean useGlobalDataSourceStat = false;
    @Setter
    @Getter
    private boolean logDifferentThread = true;
    @Setter
    @Getter
    private volatile DataSourceDisableException disableException = null;

    public DataSource() {
        this(false);
    }


    public DataSource(boolean fairLock) {
        super(fairLock);

        configFromPropety(System.getProperties());
    }

    public static Boolean getBoolean(Properties properties, String key) {
        String property = properties.getProperty(key);
        if ("true".equals(property)) {
            return Boolean.TRUE;
        } else if ("false".equals(property)) {
            return Boolean.FALSE;
        }
        return null;
    }

    public void configFromPropety(Properties properties) {

        {
            String property = properties.getProperty("druid.url");
            if (property != null) {
                this.setUrl(property);
            }
        }
        {
            String property = properties.getProperty("druid.username");
            if (property != null) {
                this.setUsername(property);
            }
        }
        {
            String property = properties.getProperty("druid.password");
            if (property != null) {
                this.setPassword(property);
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.testWhileIdle");
            if (value != null) {
                this.testWhileIdle = value;
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.testOnBorrow");
            if (value != null) {
                this.testOnBorrow = value;
            }
        }
        {
            String property = properties.getProperty("druid.validationQuery");
            if (property != null && property.length() > 0) {
                this.setValidationQuery(property);
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.useGlobalDataSourceStat");
            if (value != null) {
                this.setUseGlobalDataSourceStat(value);
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.useGloalDataSourceStat"); // compatible for early versions
            if (value != null) {
                this.setUseGlobalDataSourceStat(value);
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.asyncInit"); // compatible for early versions
            if (value != null) {
                this.setAsyncInit(value);
            }
        }
        {

        }
        {
            String property = properties.getProperty(DRUID_TIME_BETWEEN_LOG_STATS_MILLIS);
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setTimeBetweenLogStatsMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property '" + DRUID_TIME_BETWEEN_LOG_STATS_MILLIS + "'", e);
                }
            }
        }

        {
            Boolean value = getBoolean(properties, "druid.clearFiltersEnable");
            if (value != null) {
                this.setClearFiltersEnable(value);
            }
        }

        {
            String property = properties.getProperty("druid.notFullTimeoutRetryCount");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setNotFullTimeoutRetryCount(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.notFullTimeoutRetryCount'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.timeBetweenEvictionRunsMillis");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setTimeBetweenEvictionRunsMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.timeBetweenEvictionRunsMillis'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.maxWaitThreadCount");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setMaxWaitThreadCount(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.maxWaitThreadCount'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.maxWait");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setMaxWait(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.maxWait'", e);
                }
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.failFast");
            if (value != null) {
                this.setFailFast(value);
            }
        }
        {
            String property = properties.getProperty("druid.phyTimeoutMillis");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setPhyTimeoutMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.phyTimeoutMillis'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.maxUseCount");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setMaxUseCount(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.maxUseCount'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.minEvictableIdleTimeMillis");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setMinEvictableIdleTimeMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.minEvictableIdleTimeMillis'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.maxEvictableIdleTimeMillis");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setMaxEvictableIdleTimeMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.maxEvictableIdleTimeMillis'", e);
                }
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.keepAlive");
            if (value != null) {
                this.setKeepAlive(value);
            }
        }
        {
            String property = properties.getProperty("druid.keepAliveBetweenTimeMillis");
            if (property != null && property.length() > 0) {
                try {
                    long value = Long.parseLong(property);
                    this.setKeepAliveBetweenTimeMillis(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.keepAliveBetweenTimeMillis'", e);
                }
            }
        }


        {
            Boolean value = getBoolean(properties, "druid.useUnfairLock");
            if (value != null) {
                this.setUseUnfairLock(value);
            }
        }

        {
            String property = properties.getProperty("druid.initialSize");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setInitialSize(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.initialSize'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.minIdle");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setMinIdle(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.minIdle'", e);
                }
            }
        }
        {
            String property = properties.getProperty("druid.maxActive");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setMaxActive(value);
                } catch (NumberFormatException e) {
                    log.error("illegal property 'druid.maxActive'", e);
                }
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.killWhenSocketReadTimeout");
            if (value != null) {
                setKillWhenSocketReadTimeout(value);
            }
        }
        {
            String property = properties.getProperty("druid.connectProperties");
            if (property != null) {
                this.setConnectionProperties(property);
            }
        }

        {
            String property = properties.getProperty("druid.initConnectionSqls");
            if (property != null && property.length() > 0) {
                setConnectionInitSqlList(null);
            }
        }

    }

    public void restart() throws SQLException {
        lock.lock();
        try {
            if (activeCount > 0) {
                throw new SQLException("can not restart, activeCount not zero. " + activeCount);
            }
            if (log.isInfoEnabled()) {
                log.info("dataSource restart");
            }

            this.close();
            this.resetStat();
            this.inited = false;
            this.enable = true;
            this.closed = false;
        } finally {
            lock.unlock();
        }
    }

    public void resetStat() {
        if (!isResetStatEnable()) {
            return;
        }

        lock.lock();
        try {
            connectCount = 0;
            closeCount = 0;
            discardCount = 0;
            recycleCount = 0;
            createCount = 0L;
            directCreateCount = 0;
            destroyCount = 0L;
            removeAbandonedCount = 0;
            notEmptyWaitCount = 0;
            notEmptySignalCount = 0L;
            notEmptyWaitNanos = 0;

            activePeak = activeCount;
            activePeakTime = 0;
            poolingPeak = 0;
            createTimespan = 0;
            lastError = null;
            lastErrorTimeMillis = 0;
            lastCreateError = null;
            lastCreateErrorTimeMillis = 0;
        } finally {
            lock.unlock();
        }

        connectErrorCountUpdater.set(this, 0);
        errorCountUpdater.set(this, 0);
        commitCountUpdater.set(this, 0);
        rollbackCountUpdater.set(this, 0);
        startTransactionCountUpdater.set(this, 0);
        cachedPreparedStatementHitCountUpdater.set(this, 0);
        closedPreparedStatementCountUpdater.set(this, 0);
        preparedStatementCountUpdater.set(this, 0);

        cachedPreparedStatementDeleteCountUpdater.set(this, 0);
        recycleErrorCountUpdater.set(this, 0);

        resetCountUpdater.incrementAndGet(this);
    }

    public void setEnable(boolean enable) {
        lock.lock();
        try {
            this.enable = enable;
            if (!enable) {
                notEmpty.signalAll();
                notEmptySignalCount++;
            }
        } finally {
            lock.unlock();
        }
    }

    public void setMaxActive(int maxActive) {
        if (this.maxActive == maxActive) {
            return;
        }

        if (maxActive == 0) {
            throw new IllegalArgumentException("maxActive can't not set zero");
        }

        if (!inited) {
            this.maxActive = maxActive;
            return;
        }

        if (maxActive < this.minIdle) {
            throw new IllegalArgumentException("maxActive less than minIdle, " + maxActive + " < " + this.minIdle);
        }

        if (log.isInfoEnabled()) {
            log.info("maxActive changed : " + this.maxActive + " -> " + maxActive);
        }

        lock.lock();
        try {
            int allCount = this.poolingCount + this.activeCount;

            if (maxActive > allCount) {
                this.connectionHolders = Arrays.copyOf(this.connectionHolders, maxActive);
                evictConnectionHolders = new ConnectionHolder[maxActive];
                keepAliveConnectionHolders = new ConnectionHolder[maxActive];
            } else {
                this.connectionHolders = Arrays.copyOf(this.connectionHolders, allCount);
                evictConnectionHolders = new ConnectionHolder[allCount];
                keepAliveConnectionHolders = new ConnectionHolder[allCount];
            }

            this.maxActive = maxActive;
        } finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("rawtypes")
    public void setConnectProperties(Properties properties) {
        if (properties == null) {
            properties = new Properties();
        }

        boolean equals;
        if (properties.size() == this.connectProperties.size()) {
            equals = true;
            for (Map.Entry entry : properties.entrySet()) {
                Object value = this.connectProperties.get(entry.getKey());
                Object entryValue = entry.getValue();
                if (value == null && entryValue != null) {
                    equals = false;
                    break;
                }

                if (!value.equals(entry.getValue())) {
                    equals = false;
                    break;
                }
            }
        } else {
            equals = false;
        }

        if (!equals) {
            if (inited && log.isInfoEnabled()) {
                log.info("connectProperties changed : " + this.connectProperties + " -> " + properties);
            }

            configFromPropety(properties);


        }

        this.connectProperties = properties;
    }

    public void init() throws SqlException {
        if (inited) {
            return;
        }
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SqlException("interrupt", e);
        }


        try {
            if (inited) {
                return;
            }


            connectionIdSeedUpdater.addAndGet(this, 1);


            {
                boolean cacheServerConfigurationSet = false;
                if (this.connectProperties.containsKey("cacheServerConfiguration")) {
                    cacheServerConfigurationSet = true;
                } else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {
                    cacheServerConfigurationSet = true;
                }
                if (cacheServerConfigurationSet) {
                    this.connectProperties.put("cacheServerConfiguration", "true");
                }
            }

            if (maxActive <= 0) {
                throw new IllegalArgumentException("illegal maxActive " + maxActive);
            }

            if (maxActive < minIdle) {
                throw new IllegalArgumentException("illegal maxActive " + maxActive);
            }

            if (getInitialSize() > maxActive) {
                throw new IllegalArgumentException(
                        "illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
            }

            if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) {
                throw new IllegalArgumentException(
                        "timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true");
            }

            if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
                throw new SqlException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
            }


            connectionHolders = new ConnectionHolder[maxActive];
            evictConnectionHolders = new ConnectionHolder[maxActive];
            keepAliveConnectionHolders = new ConnectionHolder[maxActive];

            SqlException connectError = null;

            if (createScheduler != null && asyncInit) {
                for (int i = 0; i < initialSize; ++i) {
                    submitCreateTask(true);
                }
            } else if (!asyncInit) {
                // init connectionHolders
                while (poolingCount < initialSize) {
                    try {
                        Connection connection = createConnection();
                        ConnectionHolder connectionHolder = new ConnectionHolder(this, connection);
                        connectionHolders[poolingCount++] = connectionHolder;
                    } catch (SqlException ex) {
                        log.error("init datasource error, url: " + this.getJdbcUrl(), ex);
                        if (initExceptionThrow) {
                            connectError = ex;
                            break;
                        } else {
                            Thread.sleep(3000);
                        }
                    }
                }

                if (poolingCount > 0) {
                    poolingPeak = poolingCount;
                    poolingPeakTime = System.currentTimeMillis();
                }
            }


            createAndStartCreatorThread();
            createAndStartDestroyThread();

            iniCountDownLatch.await();


            initedTime = new Date();


            if (connectError != null && poolingCount == 0) {
                throw connectError;
            }

            if (keepAlive) {
                // async fill to minIdle
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        submitCreateTask(true);
                    }
                } else {
                    this.emptySignal();
                }
            }

        } catch (SqlException e) {
            log.error("dataSource init error", e);
            throw e;
        } catch (InterruptedException e) {
            log.error("{dataSource init error", e);
            throw new SqlException(e.getMessage(), e);
        } catch (RuntimeException e) {
            log.error("{dataSource init error", e);
            throw e;
        } catch (Error e) {
            log.error("dataSource init error", e);
            throw e;

        } finally {
            inited = true;
            lock.unlock();


        }
    }

    private void submitCreateTask(boolean initTask) {
        createConnectionTaskCount++;
        CreateConnectionTask createConnectionTask = new CreateConnectionTask(this, initTask);
        if (createConnectionTasks == null) {
            createConnectionTasks = new long[8];
        }

        boolean putted = false;
        for (int i = 0; i < createConnectionTasks.length; ++i) {
            if (createConnectionTasks[i] == 0) {
                createConnectionTasks[i] = createConnectionTask.createConnectionTaskId;
                putted = true;
                break;
            }
        }
        if (!putted) {
            long[] array = new long[createConnectionTasks.length * 3 / 2];
            System.arraycopy(createConnectionTasks, 0, array, 0, createConnectionTasks.length);
            array[createConnectionTasks.length] = createConnectionTask.createConnectionTaskId;
            createConnectionTasks = array;
        }

        this.createSchedulerFuture = createScheduler.submit(createConnectionTask);
    }

    public boolean clearCreateConnectionTaskId(long createConnectionTaskId) {
        if (createConnectionTasks == null) {
            return false;
        }

        if (createConnectionTaskId == 0) {
            return false;
        }

        for (int i = 0; i < createConnectionTasks.length; i++) {
            if (createConnectionTasks[i] == createConnectionTaskId) {
                createConnectionTasks[i] = 0;
                createConnectionTaskCount--;

                if (createConnectionTaskCount < 0) {
                    createConnectionTaskCount = 0;
                }

                if (createConnectionTaskCount == 0 && createConnectionTasks.length > 8) {
                    createConnectionTasks = new long[8];
                }
                return true;
            }
        }

        if (log.isWarnEnabled()) {
            log.warn("clear create task failed : " + createConnectionTaskId);
        }

        return false;
    }

    public void createAndStartDestroyThread() {
        destroyConnectionTask = new DestroyConnectionTask(this);

        if (destroyScheduler != null) {
            long period = timeBetweenEvictionRunsMillis;
            if (period <= 0) {
                period = 1000;
            }
            destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyConnectionTask, period, period,
                    TimeUnit.MILLISECONDS);
            iniCountDownLatch.countDown();
            return;
        }

        String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
        destroyConnectionThread = new DestroyConnectionThread(this, threadName);
        destroyConnectionThread.start();
    }

    public void createAndStartCreatorThread() {
        if (createScheduler == null) {
            String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this);
            createConnectionThread = new CreateConnectionThread(this, threadName);
            createConnectionThread.start();
            return;
        }

        iniCountDownLatch.countDown();
    }

    public drds.datasource.connection_pool.api.Connection getConnection() throws SqlException {
        return getConnection(maxWait);
    }

    public drds.datasource.connection_pool.api.Connection getConnection(long maxWaitMillis) throws SqlException {
        init();
        return getPooledConnectionDirect(maxWaitMillis);
    }

    /**
     * 底层获取数据库连接
     */
    public PooledConnection getPooledConnectionDirect(long maxWaitMillis) throws SqlException {
        int notFullTimeoutRetryCnt = 0;
        for (; ; ) {
            // handle notFullTimeoutRetry
            PooledConnection pooledConnection;
            try {
                pooledConnection = getPooledConnection(maxWaitMillis);
            } catch (GetConnectionTimeoutException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    if (log.isWarnEnabled()) {
                        log.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }

            if (testOnBorrow) {
                boolean validate = testConnectionInternal(pooledConnection.connectionHolder, pooledConnection.connection);
                if (!validate) {
                    if (log.isDebugEnabled()) {
                        log.debug("skip not validate connection.");
                    }

                    Connection connection = pooledConnection.connection;
                    discardConnection(connection);
                    continue;
                }
            } else {
                Connection connection = pooledConnection.connection;
                try {
                    if (pooledConnection.connection.isClosed()) {
                        discardConnection(null); // 传入null，避免重复关闭
                        continue;
                    }
                } catch (SQLException e) {
                    if (Constants.developMode) {
                        e.printStackTrace();
                    }
                    log.error("discardConnection", e);
                    throw new SqlException(e);
                }

                if (testWhileIdle) {
                    final ConnectionHolder connectionHolder = pooledConnection.connectionHolder;
                    long currentTimeMillis = System.currentTimeMillis();
                    long lastActiveTimeMillis = connectionHolder.lastActiveTimeMillis;
                    long lastKeepTimeMillis = connectionHolder.lastKeepTimeMillis;

                    if (lastKeepTimeMillis > lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastKeepTimeMillis;
                    }

                    long idleMillis = currentTimeMillis - lastActiveTimeMillis;

                    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }

                    if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 // unexcepted branch
                    ) {
                        boolean validate = testConnectionInternal(pooledConnection.connectionHolder, pooledConnection.connection);
                        if (!validate) {
                            if (log.isDebugEnabled()) {
                                log.debug("skip not validate connection.");
                            }

                            discardConnection(connection);
                            continue;
                        }
                    }
                }
            }

            if (supportRemoveAbandoned) {
                StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
                pooledConnection.stackTraceElements = stackTraceElements;
                pooledConnection.setConnectedTimeNano();
                pooledConnection.traceEnable = true;

                activeConnectionLock.lock();
                try {
                    activeConnections.put(pooledConnection, PRESENT);
                } finally {
                    activeConnectionLock.unlock();
                }
            }

            return pooledConnection;
        }
    }

    /**
     * 抛弃连接，不进行回收，而是抛弃
     *
     * @param connection
     */
    public void discardConnection(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {

            }
        }
        //
        lock.lock();
        try {
            activeCount--;
            discardCount++;
            if (activeCount <= minIdle) {
                emptySignal();
            }
        } finally {
            lock.unlock();
        }
    }

    private PooledConnection getPooledConnection(long maxWait) throws SqlException {
        if (closed) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
        }

        if (!enable) {
            connectErrorCountUpdater.incrementAndGet(this);

            if (disableException != null) {
                throw disableException;
            }

            throw new DataSourceDisableException();
        }

        final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
        final int maxWaitThreadCount = this.maxWaitThreadCount;

        ConnectionHolder connectionHolder;

        for (boolean createDirect = false; ; ) {
            if (createDirect) {
                createStartNanosUpdater.set(this, System.nanoTime());
                if (creatingCountUpdater.compareAndSet(this, 0, 1)) {
                    Connection connection = createConnection();
                    connectionHolder = new ConnectionHolder(this, connection);
                    connectionHolder.lastActiveTimeMillis = System.currentTimeMillis();

                    creatingCountUpdater.decrementAndGet(this);
                    directCreateCountUpdater.incrementAndGet(this);


                    boolean discard = false;
                    lock.lock();
                    try {
                        if (activeCount < maxActive) {
                            activeCount++;
                            if (activeCount > activePeak) {
                                activePeak = activeCount;
                                activePeakTime = System.currentTimeMillis();
                            }
                            break;
                        } else {
                            discard = true;
                        }
                    } finally {
                        lock.unlock();
                    }

                    if (discard) {
                        JdbcUtils.close(connection);
                    }
                }
            }

            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SqlException("interrupt", e);
            }

            try {
                if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) {
                    connectErrorCountUpdater.incrementAndGet(this);
                    throw new SqlException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                            + lock.getQueueLength());
                }

                if (onFatalError && onFatalErrorMaxActive > 0 && activeCount >= onFatalErrorMaxActive) {
                    connectErrorCountUpdater.incrementAndGet(this);

                    StringBuilder sb = new StringBuilder();
                    sb.append("onFatalError, activeCount ").append(activeCount).append(", onFatalErrorMaxActive ")
                            .append(onFatalErrorMaxActive);

                    if (lastFatalErrorTimeMillis > 0) {
                        sb.append(", time '")
                                .append(new Date(lastFatalErrorTimeMillis))
                                .append("'");
                    }

                    if (lastFatalErrorSql != null) {
                        sb.append(", sql \n").append(lastFatalErrorSql);
                    }

                    throw new SqlException(sb.toString(), lastFatalError);
                }

                connectCount++;

                if (createScheduler != null && poolingCount == 0 && activeCount < maxActive
                        && creatingCountUpdater.get(this) == 0
                        && createScheduler instanceof ScheduledThreadPoolExecutor) {
                    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
                    if (executor.getQueue().size() > 0) {
                        createDirect = true;
                        continue;
                    }
                }

                if (maxWait > 0) {
                    connectionHolder = pollLast(nanos);
                } else {
                    connectionHolder = takeLast();
                }

                if (connectionHolder != null) {
                    activeCount++;
                    if (activeCount > activePeak) {
                        activePeak = activeCount;
                        activePeakTime = System.currentTimeMillis();
                    }
                }
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SqlException(e.getMessage(), e);
            } catch (SqlException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw e;
            } finally {
                lock.unlock();
            }

            break;
        }

        if (connectionHolder == null) {
            long waitNanos = waitNanosLocal.get();

            StringBuilder sb = new StringBuilder(128);
            sb.append("wait millis ")//
                    .append(waitNanos / (1000 * 1000))//
                    .append(", active ").append(activeCount)//
                    .append(", maxActive ").append(maxActive)//
                    .append(", creating ").append(creatingCount)//
            ;
            if (creatingCount > 0 && createStartNanos > 0) {
                long createElapseMillis = (System.nanoTime() - createStartNanos) / (1000 * 1000);
                if (createElapseMillis > 0) {
                    sb.append(", createElapseMillis ").append(createElapseMillis);
                }
            }

            if (createErrorCount > 0) {
                sb.append(", createErrorCount ").append(createErrorCount);
            }


            String errorMessage = sb.toString();

            if (this.createError != null) {
                throw new GetConnectionTimeoutException(errorMessage, createError);
            } else {
                throw new GetConnectionTimeoutException(errorMessage);
            }
        }

        connectionHolder.incrementUseCount();

        PooledConnection pooledConnection = new PooledConnection(connectionHolder);
        return pooledConnection;
    }

    public void handleConnectionException(PooledConnection pooledConnection, SQLException e, String sql)
            throws SqlException {


        errorCountUpdater.incrementAndGet(this);
        lastError = e;
        lastErrorTimeMillis = System.currentTimeMillis();


        // exceptionChecker.isFatalException
        if (exceptionChecker != null && exceptionChecker.isFatalException(e)) {
            handleFatalException(pooledConnection, e, sql);
        }
        throw new SqlException(e);
    }

    public final void handleFatalException(PooledConnection pooledConnection, SQLException e, String sql)
            throws SqlException {
        final ConnectionHolder connectionHolder = pooledConnection.connectionHolder;

        if (pooledConnection.isTraceEnable()) {
            activeConnectionLock.lock();
            try {
                if (pooledConnection.isTraceEnable()) {
                    activeConnections.remove(pooledConnection);
                    pooledConnection.setTraceEnable(false);
                }
            } finally {
                activeConnectionLock.unlock();
            }
        }

        long lastErrorTimeMillis = this.lastErrorTimeMillis;
        if (lastErrorTimeMillis == 0) {
            lastErrorTimeMillis = System.currentTimeMillis();
        }

        if (sql != null && sql.length() > 1024) {
            sql = sql.substring(0, 1024);
        }

        boolean requireDiscard = false;
        final ReentrantLock lock = pooledConnection.lock;
        lock.lock();
        try {
            if ((!pooledConnection.isClosed()) || !pooledConnection.isDisable()) {
                connectionHolder.setDiscard(true);
                pooledConnection.disable(e);
                requireDiscard = true;
            }

            lastFatalErrorTimeMillis = lastErrorTimeMillis;
            onFatalError = true;
            lastFatalError = e;
            lastFatalErrorSql = sql;
        } finally {
            lock.unlock();
        }

        if (requireDiscard) {
            if (connectionHolder.statementList != null) {
                connectionHolder.lock.lock();
                try {
                    for (drds.datasource.connection_pool.api.Statement statement : connectionHolder.statementList) {
                        statement.close();
                    }
                } finally {
                    connectionHolder.lock.unlock();
                }
            }

            this.discardConnection(connectionHolder.getConnection());
            connectionHolder.setDiscard(true);
        }

        log.error("discard connection", e);
    }

    /**
     * 回收连接
     */
    public void recycle(PooledConnection pooledConnection) throws SqlException {
        final ConnectionHolder connectionHolder = pooledConnection.connectionHolder;

        if (connectionHolder == null) {
            log.warn("connectionHolder is null");
            return;
        }

        if (logDifferentThread //
                && (!isAsyncCloseConnectionEnable()) //
                && pooledConnection.ownerThread != Thread.currentThread()//
        ) {
            log.warn("get/close not same thread");
        }

        final Connection physicalConnection = connectionHolder.connection;

        if (pooledConnection.traceEnable) {
            Object oldInfo = null;
            activeConnectionLock.lock();
            try {
                if (pooledConnection.traceEnable) {
                    oldInfo = activeConnections.remove(pooledConnection);
                    pooledConnection.traceEnable = false;
                }
            } finally {
                activeConnectionLock.unlock();
            }
            if (oldInfo == null) {
                if (log.isWarnEnabled()) {
                    log.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
                }
            }
        }

        final boolean isAutoCommit = connectionHolder.underlyingAutoCommit;

        final boolean testOnReturn = this.testOnReturn;
        //如果出现这种情况先进行回滚,然后再清理物理连接
        try {
            // check need to rollback?
            if ((!isAutoCommit)) {
                pooledConnection.rollback();
            }

            // reset connectionHolder, restore default settings, clear warnings
            boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
            if (!isSameThread) {
                final ReentrantLock lock = pooledConnection.lock;
                lock.lock();
                try {
                    connectionHolder.reset();
                } finally {
                    lock.unlock();
                }
            } else {
                connectionHolder.reset();
            }

            if (connectionHolder.discard) {
                return;
            }

            if (maxUseCount > 0 && connectionHolder.useCount >= maxUseCount) {
                discardConnection(connectionHolder.connection);
                return;
            }

            if (physicalConnection.isClosed()) {
                lock.lock();
                try {
                    activeCount--;
                    closeCount++;
                } finally {
                    lock.unlock();
                }
                return;
            }

            if (testOnReturn) {
                boolean validate = testConnectionInternal(connectionHolder, physicalConnection);
                if (!validate) {
                    JdbcUtils.close(physicalConnection);

                    destroyCountUpdater.incrementAndGet(this);

                    lock.lock();
                    try {
                        activeCount--;
                        closeCount++;
                    } finally {
                        lock.unlock();
                    }
                    return;
                }
            }

            if (!enable) {
                discardConnection(connectionHolder.connection);
                return;
            }

            boolean result;
            final long currentTimeMillis = System.currentTimeMillis();

            if (phyTimeoutMillis > 0) {
                long phyConnectTimeMillis = currentTimeMillis - connectionHolder.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    discardConnection(connectionHolder.connection);
                    return;
                }
            }

            lock.lock();
            try {
                activeCount--;
                closeCount++;

                result = putLast(connectionHolder, currentTimeMillis);
                recycleCount++;
            } finally {
                lock.unlock();
            }

            if (!result) {
                JdbcUtils.close(connectionHolder.connection);
                log.info("connection recyle failed.");
            }
        } catch (Throwable e) {


            if (!connectionHolder.discard) {
                this.discardConnection(physicalConnection);
                connectionHolder.discard = true;
            }

            log.error("recyle error", e);
            recycleErrorCountUpdater.incrementAndGet(this);
        }
    }

    public void close() {


        lock.lock();
        try {
            if (this.closed) {
                return;
            }

            if (!this.inited) {
                return;
            }

            this.closing = true;


            if (createConnectionThread != null) {
                createConnectionThread.interrupt();
            }

            if (destroyConnectionThread != null) {
                destroyConnectionThread.interrupt();
            }

            if (createSchedulerFuture != null) {
                createSchedulerFuture.cancel(true);
            }

            if (destroySchedulerFuture != null) {
                destroySchedulerFuture.cancel(true);
            }

            for (int i = 0; i < poolingCount; ++i) {
                ConnectionHolder connectionHolder = connectionHolders[i];
                Connection connection = connectionHolder.getConnection();
                try {
                    connection.close();
                } catch (Exception ex) {
                    log.warn("close connection error", ex);
                }
                connectionHolders[i] = null;
                destroyCountUpdater.incrementAndGet(this);
            }
            poolingCount = 0;


            enable = false;
            notEmpty.signalAll();
            notEmptySignalCount++;

            this.closed = true;
            this.closeTimeMillis = System.currentTimeMillis();

            disableException = new DataSourceDisableException();


        } finally {
            lock.unlock();
        }

        if (log.isInfoEnabled()) {
            log.info("dataSource closed");
        }
    }

    boolean putLast(ConnectionHolder connectionHolder, long lastActiveTimeMillis) {
        if (poolingCount >= maxActive) {
            return false;
        }

        connectionHolder.lastActiveTimeMillis = lastActiveTimeMillis;
        connectionHolders[poolingCount] = connectionHolder;
        incrementPoolingCount();

        if (poolingCount > poolingPeak) {
            poolingPeak = poolingCount;
            poolingPeakTime = lastActiveTimeMillis;
        }

        notEmpty.signal();
        notEmptySignalCount++;

        return true;
    }

    ConnectionHolder takeLast() throws InterruptedException, SqlException {
        try {
            while (poolingCount == 0) {
                emptySignal(); // send signal to CreateThread create connection

                if (failFast && isFailContinuous()) {
                    throw new DataSourceNotAvailableException(createError);
                }

                notEmptyWaitThreadCount++;
                if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
                    notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
                }
                try {
                    notEmpty.await(); // signal by recycle or creator
                } finally {
                    notEmptyWaitThreadCount--;
                }
                notEmptyWaitCount++;

                if (!enable) {
                    connectErrorCountUpdater.incrementAndGet(this);
                    if (disableException != null) {
                        throw disableException;
                    }

                    throw new DataSourceDisableException();
                }
            }
        } catch (InterruptedException ie) {
            notEmpty.signal(); // propagate to non-interrupted thread
            notEmptySignalCount++;
            throw ie;
        }

        decrementPoolingCount();
        ConnectionHolder lastConnectionHolder = connectionHolders[poolingCount];
        connectionHolders[poolingCount] = null;

        return lastConnectionHolder;
    }

    private ConnectionHolder pollLast(long nanos) throws InterruptedException, SqlException {
        long estimate = nanos;

        for (; ; ) {
            if (poolingCount == 0) {
                emptySignal(); // send signal to CreateThread create connection

                if (failFast && isFailContinuous()) {
                    throw new DataSourceNotAvailableException(createError);
                }

                if (estimate <= 0) {
                    waitNanosLocal.set(nanos - estimate);
                    return null;
                }

                notEmptyWaitThreadCount++;
                if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
                    notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
                }

                try {
                    long startEstimate = estimate;
                    estimate = notEmpty.awaitNanos(estimate); // signal by
                    // recycle or
                    // creator
                    notEmptyWaitCount++;
                    notEmptyWaitNanos += (startEstimate - estimate);

                    if (!enable) {
                        connectErrorCountUpdater.incrementAndGet(this);

                        if (disableException != null) {
                            throw disableException;
                        }

                        throw new DataSourceDisableException();
                    }
                } catch (InterruptedException ie) {
                    notEmpty.signal(); // propagate to non-interrupted thread
                    notEmptySignalCount++;
                    throw ie;
                } finally {
                    notEmptyWaitThreadCount--;
                }

                if (poolingCount == 0) {
                    if (estimate > 0) {
                        continue;
                    }

                    waitNanosLocal.set(nanos - estimate);
                    return null;
                }
            }

            decrementPoolingCount();
            ConnectionHolder lastConnectionHolder = connectionHolders[poolingCount];
            connectionHolders[poolingCount] = null;

            long waitNanos = nanos - estimate;
            lastConnectionHolder.setLastNotEmptyWaitNanos(waitNanos);

            return lastConnectionHolder;
        }
    }

    private final void decrementPoolingCount() {
        poolingCount--;
    }

    private final void incrementPoolingCount() {
        poolingCount++;
    }

    public long getConnectCount() {
        lock.lock();
        try {
            return connectCount;
        } finally {
            lock.unlock();
        }
    }

    public long getConnectErrorCount() {
        return connectErrorCountUpdater.get(this);
    }

    public int getPoolingCount() {
        lock.lock();
        try {
            return poolingCount;
        } finally {
            lock.unlock();
        }
    }

    public int getPoolingPeak() {
        lock.lock();
        try {
            return poolingPeak;
        } finally {
            lock.unlock();
        }
    }

    public Date getPoolingPeakTime() {
        if (poolingPeakTime <= 0) {
            return null;
        }

        return new Date(poolingPeakTime);
    }

    public long getRecycleCount() {
        return recycleCount;
    }

    public int getActiveCount() {
        lock.lock();
        try {
            return activeCount;
        } finally {
            lock.unlock();
        }
    }

    public DataSourceState getDataSourceState() {
        DataSourceState dataSourceState = new DataSourceState();

        lock.lock();
        try {
            dataSourceState.setPoolingCount(this.poolingCount);
            dataSourceState.setPoolingPeak(this.poolingPeak);
            dataSourceState.setPoolingPeakTime(this.poolingPeakTime);

            dataSourceState.setActiveCount(this.activeCount);
            dataSourceState.setActivePeak(this.activePeak);
            dataSourceState.setActivePeakTime(this.activePeakTime);

            dataSourceState.setConnectCount(this.connectCount);
            dataSourceState.setCloseCount(this.closeCount);
            dataSourceState.setWaitThreadCount(lock.getWaitQueueLength(notEmpty));
            dataSourceState.setNotEmptyWaitCount(this.notEmptyWaitCount);
            dataSourceState.setNotEmptyWaitNanos(this.notEmptyWaitNanos);
            dataSourceState.setKeepAliveCheckCount(this.keepAliveCheckCount);

            // reset
            this.poolingPeak = 0;
            this.poolingPeakTime = 0;
            this.activePeak = 0;
            this.activePeakTime = 0;
            this.connectCount = 0;
            this.closeCount = 0;
            this.keepAliveCheckCount = 0;

            this.notEmptyWaitCount = 0;
            this.notEmptyWaitNanos = 0;
        } finally {
            lock.unlock();
        }


        dataSourceState.setUserName(this.getUsername());


        dataSourceState.setInitialSize(this.getInitialSize());
        dataSourceState.setMinIdle(this.getMinIdle());
        dataSourceState.setMaxActive(this.getMaxActive());

        dataSourceState.setQueryTimeout(this.getQueryTimeout());
        dataSourceState.setTransactionQueryTimeout(this.getTransactionQueryTimeout());


        dataSourceState.setTestOnBorrow(this.testOnBorrow);
        dataSourceState.setTestOnReturn(this.testOnReturn);
        dataSourceState.setTestWhileIdle(this.testWhileIdle);


        dataSourceState.setLogicConnectErrorCount(connectErrorCountUpdater.getAndSet(this, 0));

        dataSourceState.setPhysicalConnectCount(createCountUpdater.getAndSet(this, 0));
        dataSourceState.setPhysicalCloseCount(destroyCountUpdater.getAndSet(this, 0));
        dataSourceState.setPhysicalConnectErrorCount(createErrorCountUpdater.getAndSet(this, 0));


        dataSourceState.setErrorCount(errorCountUpdater.getAndSet(this, 0));
        dataSourceState.setCommitCount(commitCountUpdater.getAndSet(this, 0));
        dataSourceState.setRollbackCount(rollbackCountUpdater.getAndSet(this, 0));

        dataSourceState.setPstmtCacheHitCount(cachedPreparedStatementHitCountUpdater.getAndSet(this, 0));
        dataSourceState.setPstmtCacheMissCount(cachedPreparedStatementMissCountUpdater.getAndSet(this, 0));

        dataSourceState.setStartTransactionCount(startTransactionCountUpdater.getAndSet(this, 0));


        dataSourceState.setRemoveAbandoned(this.isSupportRemoveAbandoned());

        return dataSourceState;
    }

    public long getRemoveAbandonedCount() {
        return removeAbandonedCount;
    }

    public boolean put(Connection connection, long createConnectionTaskId) {
        ConnectionHolder connectionHolder = null;
        try {
            connectionHolder = new ConnectionHolder(DataSource.this, connection);
        } catch (SQLException ex) {
            lock.lock();
            try {
                if (createScheduler != null) {
                    clearCreateConnectionTaskId(createConnectionTaskId);
                }
            } finally {
                lock.unlock();
            }
            log.error("create connection connectionHolder error", ex);
            return false;
        }

        return put(connectionHolder, createConnectionTaskId);
    }

    private boolean put(ConnectionHolder connectionHolder, long createTaskId) {
        lock.lock();
        try {
            if (poolingCount >= maxActive) {
                return false;
            }
            connectionHolders[poolingCount] = connectionHolder;
            incrementPoolingCount();

            if (poolingCount > poolingPeak) {
                poolingPeak = poolingCount;
                poolingPeakTime = System.currentTimeMillis();
            }

            notEmpty.signal();
            notEmptySignalCount++;

            if (createScheduler != null) {
                clearCreateConnectionTaskId(createTaskId);

                if (poolingCount + createConnectionTaskCount < notEmptyWaitThreadCount //
                        && activeCount + poolingCount + createConnectionTaskCount < maxActive) {
                    emptySignal();
                }
            }
        } finally {
            lock.unlock();
        }
        return true;
    }

    public int removeAbandoned() throws SQLException {
        int removeCount = 0;

        long currrentNanos = System.nanoTime();

        List<PooledConnection> abandonedList = new ArrayList<PooledConnection>();

        activeConnectionLock.lock();
        try {
            Iterator<PooledConnection> iterator = activeConnections.keySet().iterator();

            for (; iterator.hasNext(); ) {
                PooledConnection pooledConnection = iterator.next();

                if (pooledConnection.isRunning()) {
                    continue;
                }

                long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);

                if (timeMillis >= removeAbandonedTimeoutMillis) {
                    iterator.remove();
                    pooledConnection.setTraceEnable(false);
                    abandonedList.add(pooledConnection);
                }
            }
        } finally {
            activeConnectionLock.unlock();
        }

        if (abandonedList.size() > 0) {
            for (PooledConnection pooledConnection : abandonedList) {
                final ReentrantLock lock = pooledConnection.lock;
                lock.lock();
                try {
                    if (pooledConnection.isDisable()) {
                        continue;
                    }
                } finally {
                    lock.unlock();
                }

                pooledConnection.close();
                pooledConnection.abandond();
                removeAbandonedCount++;
                removeCount++;

                if (isLogAbandoned()) {
                    StringBuilder buf = new StringBuilder();
                    buf.append("abandon connection, owner thread: ");
                    buf.append(pooledConnection.getOwnerThread().getName());
                    buf.append(", connected at : ");
                    buf.append(pooledConnection.getConnectedTimeMillis());
                    buf.append(", open stackTrace\n");

                    StackTraceElement[] trace = pooledConnection.getStackTraceElements();
                    for (int i = 0; i < trace.length; i++) {
                        buf.append("\tat ");
                        buf.append(trace[i].toString());
                        buf.append("\n");
                    }

                    buf.append("ownerThread current state is " + pooledConnection.getOwnerThread().getState()
                            + ", current stackTrace\n");
                    trace = pooledConnection.getOwnerThread().getStackTrace();
                    for (int i = 0; i < trace.length; i++) {
                        buf.append("\tat ");
                        buf.append(trace[i].toString());
                        buf.append("\n");
                    }

                    log.error(buf.toString());
                }
            }
        }

        return removeCount;
    }

    public String getProperties() {
        Properties properties = new Properties();
        properties.putAll(connectProperties);
        if (properties.containsKey("password")) {
            properties.put("password", "******");
        }
        return properties.toString();
    }

    public void shrink() throws SQLException {
        shrink(false, false);
    }

    public void shrink(boolean checkTime) throws SQLException {
        shrink(checkTime, keepAlive);
    }

    public void shrink(boolean checkTime, boolean keepAlive) throws SQLException {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            return;
        }

        boolean needFill = false;
        int evictCount = 0;
        int keepAliveCount = 0;
        try {
            if (!inited) {
                return;
            }

            final int checkCount = poolingCount - minIdle;
            final long currentTimeMillis = System.currentTimeMillis();
            for (int i = 0; i < poolingCount; ++i) {
                ConnectionHolder connectionHolder = connectionHolders[i];

                if (checkTime) {
                    if (phyTimeoutMillis > 0) {
                        long phyConnectTimeMillis = currentTimeMillis - connectionHolder.connectTimeMillis;
                        if (phyConnectTimeMillis > phyTimeoutMillis) {
                            evictConnectionHolders[evictCount++] = connectionHolder;
                            continue;
                        }
                    }

                    long idleMillis = currentTimeMillis - connectionHolder.lastActiveTimeMillis;

                    if (idleMillis < minEvictableIdleTimeMillis && idleMillis < keepAliveBetweenTimeMillis) {
                        break;
                    }

                    if (idleMillis >= minEvictableIdleTimeMillis) {
                        if (checkTime && i < checkCount) {
                            evictConnectionHolders[evictCount++] = connectionHolder;
                            continue;
                        } else if (idleMillis > maxEvictableIdleTimeMillis) {
                            evictConnectionHolders[evictCount++] = connectionHolder;
                            continue;
                        }
                    }

                    if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {
                        keepAliveConnectionHolders[keepAliveCount++] = connectionHolder;
                    }
                } else {
                    if (i < checkCount) {
                        evictConnectionHolders[evictCount++] = connectionHolder;
                    } else {
                        break;
                    }
                }
            }

            int removeCount = evictCount + keepAliveCount;
            if (removeCount > 0) {
                System.arraycopy(connectionHolders, removeCount, connectionHolders, 0, poolingCount - removeCount);
                Arrays.fill(connectionHolders, poolingCount - removeCount, poolingCount, null);
                poolingCount -= removeCount;
            }
            keepAliveCheckCount += keepAliveCount;

            if (keepAlive && poolingCount + activeCount < minIdle) {
                needFill = true;
            }
        } finally {
            lock.unlock();
        }

        if (evictCount > 0) {
            for (int i = 0; i < evictCount; ++i) {
                ConnectionHolder connectionHolder = evictConnectionHolders[i];
                Connection connection = connectionHolder.getConnection();
                JdbcUtils.close(connection);
                destroyCountUpdater.incrementAndGet(this);
            }
            Arrays.fill(evictConnectionHolders, null);
        }

        if (keepAliveCount > 0) {
            // keep order
            for (int i = keepAliveCount - 1; i >= 0; --i) {
                ConnectionHolder connectionHolder = keepAliveConnectionHolders[i];
                Connection connection = connectionHolder.getConnection();


                boolean validate = false;
                try {
                    this.validateConnection(connection);
                    validate = true;
                } catch (SQLException e) {
                    JdbcUtils.log(log, "validateConnection(connection)", e);
                    // skip
                }
                boolean discard = !validate;
                if (validate) {
                    connectionHolder.lastKeepTimeMillis = System.currentTimeMillis();
                    boolean putOk = put(connectionHolder, 0L);
                    if (!putOk) {
                        discard = true;
                    }
                }

                if (discard) {
                    try {
                        connection.close();
                    } catch (Exception e) {
                        // skip
                    }

                    lock.lock();
                    try {
                        discardCount++;

                        if (activeCount + poolingCount <= minIdle) {
                            emptySignal();
                        }
                    } finally {
                        lock.unlock();
                    }
                }
            }
            //this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
            Arrays.fill(keepAliveConnectionHolders, null);
        }

        if (needFill) {
            lock.lock();
            try {
                int fillCount = minIdle - (activeCount + poolingCount + createConnectionTaskCount);
                for (int i = 0; i < fillCount; ++i) {
                    emptySignal();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public int getWaitThreadCount() {
        lock.lock();
        try {
            return lock.getWaitQueueLength(notEmpty);
        } finally {
            lock.unlock();
        }
    }

    public int getNotEmptyWaitThreadCount() {
        lock.lock();
        try {
            return notEmptyWaitThreadCount;
        } finally {
            lock.unlock();
        }
    }

    public int getNotEmptyWaitThreadPeak() {
        lock.lock();
        try {
            return notEmptyWaitThreadPeak;
        } finally {
            lock.unlock();
        }
    }

    public long getNotEmptyWaitMillis() {
        return notEmptyWaitNanos / (1000 * 1000);
    }

    public int getActivePeak() {
        return activePeak;
    }

    public Date getActivePeakTime() {
        if (activePeakTime <= 0) {
            return null;
        }

        return new Date(activePeakTime);
    }

    public drds.datasource.connection_pool.api.Connection tryGetConnection() throws SQLException {
        if (poolingCount == 0) {
            return null;
        }
        return getConnection();
    }


    public int fill() throws SQLException {
        return this.fill(this.maxActive);
    }


    public int fill(int toCount) throws SQLException {
        if (closed) {
            throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
        }

        if (toCount < 0) {
            throw new IllegalArgumentException("toCount can'e not be less than zero");
        }

        init();

        if (toCount > this.maxActive) {
            toCount = this.maxActive;
        }

        int fillCount = 0;
        for (; ; ) {
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SQLException("interrupt", e);
            }

            boolean fillable = this.isFillable(toCount);

            lock.unlock();

            if (!fillable) {
                break;
            }

            ConnectionHolder connectionHolder;
            try {
                Connection connection = createConnection();
                connectionHolder = new ConnectionHolder(this, connection);
            } catch (SQLException e) {
                log.error("fill connection error, url: " + this.jdbcUrl, e);
                connectErrorCountUpdater.incrementAndGet(this);
                throw e;
            }

            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SQLException("interrupt", e);
            }

            try {
                if (!this.isFillable(toCount)) {
                    JdbcUtils.close(connectionHolder.getConnection());
                    log.info("fill connectionHolders skip.");
                    break;
                }
                this.putLast(connectionHolder, System.currentTimeMillis());
                fillCount++;
            } finally {
                lock.unlock();
            }
        }

        if (log.isInfoEnabled()) {
            log.info("fill " + fillCount + " connectionHolders");
        }

        return fillCount;
    }

    private boolean isFillable(int toCount) {
        int currentCount = this.poolingCount + this.activeCount;
        return currentCount < toCount && currentCount < this.maxActive;
    }

    public boolean isFull() {
        lock.lock();
        try {
            return this.poolingCount + this.activeCount >= this.maxActive;
        } finally {
            lock.unlock();
        }
    }

    private void emptySignal() {
        if (createScheduler == null) {
            empty.signal();
            return;
        }

        if (createConnectionTaskCount >= maxCreateTaskCount) {
            return;
        }

        if (activeCount + poolingCount + createConnectionTaskCount >= maxActive) {
            return;
        }
        submitCreateTask(false);
    }

}
